package com.xyazm.order.statemachine.action.aftersale;

import com.xyazm.common.constants.RedisLockKeyConstants;
import com.xyazm.common.enums.AfterSaleTypeEnum;
import com.xyazm.coupon.domain.param.ReleaseUserCouponParam;
import com.xyazm.order.domain.dto.AfterSaleStateMachineDTO;
import com.xyazm.order.domain.entity.*;
import com.xyazm.order.domain.mq.ActualRefundDTO;
import com.xyazm.order.enums.AfterSaleItemTypeEnum;
import com.xyazm.order.enums.AfterSaleStateChangeEnum;
import com.xyazm.order.exception.OrderBizException;
import com.xyazm.order.exception.OrderErrorCodeEnum;
import com.xyazm.order.statemachine.action.AfterSaleStateAction;
import com.xyazm.pay.domain.param.PayRefundParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionTemplate;

import javax.annotation.Resource;

/**
 * 实际退款 - 退款中 action
 * @author xuyou
 * @date 2023年02月26日 11:28
 */
@Slf4j
@Component
public class RefundingAction extends AfterSaleStateAction<AfterSaleStateMachineDTO> {

    @Resource
    private TransactionTemplate transactionTemplate;

    @Override
    public AfterSaleStateChangeEnum event() {
        return AfterSaleStateChangeEnum.REFUNDING;
    }

    @Override
    protected AfterSaleStateMachineDTO onStateChangeInternal(AfterSaleStateChangeEnum afterSaleStateChangeEnum,
                                                             AfterSaleStateMachineDTO afterSaleStateMachineDTO) {
        //  @Transactional无法生效，需要用编程式事务
        return transactionTemplate.execute(transactionStatus -> {
            ActualRefundDTO actualRefundDTO = afterSaleStateMachineDTO.getActualRefundDTO();
            //  分布式锁
            String afterSaleId = actualRefundDTO.getAfterSaleId();
            String key = RedisLockKeyConstants.REFUND_KEY + afterSaleId;
            boolean lock = redisLock.tryLock(key);
            if (!lock) {
                throw new OrderBizException(OrderErrorCodeEnum.REFUND_MONEY_REPEAT);
            }
            try {
                // 退款操作
                AfterSaleInfoDO afterSaleInfoDO = afterSaleInfoDAO.getOneByAfterSaleId(actualRefundDTO.getAfterSaleId());
                AfterSaleRefundDO afterSaleRefundDO = afterSaleRefundDAO.getOneByAfterSaleId(afterSaleId);
                // 1、封装调用支付退款接口的数据
                PayRefundParam payRefundParam = buildPayRefundParam(afterSaleRefundDO);
                // 2、执行退款
                payRemote.executeRefund(payRefundParam);
                // 3、取消订单流程 更新售后单状态后流程结束
                if (AfterSaleTypeEnum.RETURN_MONEY.getCode().equals(actualRefundDTO.getAfterSaleType())) {
                    updateAfterSaleStatus(afterSaleInfoDO, afterSaleStateChangeEnum);
                    return afterSaleStateMachineDTO;
                }

                // 4、手动售后流程
                // 查询售后优惠券条目
                AfterSaleItemDO afterSaleItemDO = afterSaleItemDAO.getAfterSaleOrderItem(actualRefundDTO.getOrderId(),
                        afterSaleId, actualRefundDTO.getSkuCode(), AfterSaleItemTypeEnum.AFTER_SALE_COUPON.getCode());
                // 没有使用优惠券,说明这笔售后不是尾笔 更新售后单状态后流程结束
                if (afterSaleItemDO == null) {
                    updateAfterSaleStatus(afterSaleInfoDO, afterSaleStateChangeEnum);
                    return afterSaleStateMachineDTO;
                }
                // 5、更新售后单状态
                updateAfterSaleStatus(afterSaleInfoDO, afterSaleStateChangeEnum);
                // 6、发送释放优惠券mq
                sendAfterSaleReleaseCouponMessage(afterSaleId, actualRefundDTO);
            } finally {
                redisLock.unlock(key);
            }

            return afterSaleStateMachineDTO;
        });
    }

    /**
     * 发送释放优惠券mq
     * @author xuyou
     * 2023-02-26 12:12
     * @param afterSaleId
     * @param actualRefundDTO
    */
    private void sendAfterSaleReleaseCouponMessage(String afterSaleId, ActualRefundDTO actualRefundDTO) {
        ReleaseUserCouponParam releaseUserCouponParam = buildLastOrderReleasesCouponMessage(afterSaleId, actualRefundDTO);
        mqMessageService.sendAfterSaleReleaseCouponMessage(releaseUserCouponParam);
    }


    private ReleaseUserCouponParam buildLastOrderReleasesCouponMessage(String afterSaleId, ActualRefundDTO actualRefundDTO) {
        //  组装释放优惠券权益消息数据
        String orderId = actualRefundDTO.getOrderId();
        OrderInfoDO orderInfoDO = orderInfoDAO.getByOrderId(orderId);
        ReleaseUserCouponParam releaseUserCouponRequest = new ReleaseUserCouponParam();
        releaseUserCouponRequest.setCouponId(orderInfoDO.getCouponId());
        releaseUserCouponRequest.setUserId(orderInfoDO.getUserId());
        releaseUserCouponRequest.setAfterSaleId(afterSaleId);
        return releaseUserCouponRequest;
    }

    /**
     * 更新售后单状态
     * @author xuyou
     * 2023-02-26 11:43
     * @param afterSaleInfoDO
     * @param afterSaleStateChangeEnum
    */
    private void updateAfterSaleStatus(AfterSaleInfoDO afterSaleInfoDO,
                                       AfterSaleStateChangeEnum afterSaleStateChangeEnum) {

        String afterSaleId = afterSaleInfoDO.getAfterSaleId();
        AfterSaleLogDO afterSaleLogDO = afterSaleOperateLogFactory.buildAfterSaleLog(afterSaleId, afterSaleStateChangeEnum);
        afterSaleLogDO.setOrderId(afterSaleInfoDO.getOrderId());
        Integer fromStatus = afterSaleStateChangeEnum.getFromStatus().getCode();
        Integer toStatus = afterSaleStateChangeEnum.getToStatus().getCode();
        // 更新 订单售后表
        afterSaleInfoDAO.updateStatus(afterSaleId, fromStatus, toStatus);
        // 新增 售后单变更表
        afterSaleLogDAO.save(afterSaleLogDO);
        log.info("保存售后变更记录,售后单号:{},fromStatus:{}, toStatus:{}", afterSaleId, fromStatus, toStatus);
    }

    /**
     * 封装调用支付退款接口的数据
     * @author xuyou
     * 2023-02-26 11:37
    */
    private PayRefundParam buildPayRefundParam(AfterSaleRefundDO afterSaleRefundDO) {
        PayRefundParam payRefundParam = new PayRefundParam();
        payRefundParam.setOrderId(afterSaleRefundDO.getOrderId());
        payRefundParam.setAfterSaleId(afterSaleRefundDO.getAfterSaleId());
        payRefundParam.setRefundAmount(afterSaleRefundDO.getRefundAmount());
        payRefundParam.setOutTradeNo(afterSaleRefundDO.getOutTradeNo());
        return payRefundParam;
    }

}
